// ==ClosureCompiler==
// @output_file_name default.js
// @compilation_level SIMPLE_OPTIMIZATIONS
// ==/ClosureCompiler==

/**
 * Flying Fairies - HTML5 Canvas Game
 * Adam Crowther - INT 1263
 */
 (function($){
	
	// define variables
	var touchable = 'createTouch' in document; // detect touch devices
	var canvas = document.getElementById("canvas");
	var ctx = canvas.getContext("2d");
	var fps = 30; // fallback frame rate
	var canvasWidth = canvas.width, canvasHeight = canvas.height; // canvas variables
	var mouse, clickJump = false, clickChoose = false;
	var player, totalScore, scorePerBounce = 1, prevScore = 0, prevScorePerBounce = 1, stop = true, playerStartX = canvasWidth / 2, playerStartY = canvasHeight / 1.2, chosenFairy = "magic", playerGrav = 0.475;
	var maxGroundSpeed = canvasWidth / 120, maxJumpSpeed = canvasWidth / 35;
	var clouds = [], fairies = [], items = [], powerups = [], particles = [], snowAmount = 25, snow = [], cloudList = [], presents = [], dust = [];
	var cloudWidth = canvasWidth / 14, cloudHeight = canvasHeight / 14, cloudGap = canvasHeight / 9, cloudSpeed = canvasHeight / 500, cloudStartY = -(canvasHeight / 10), cloudTicker = 1, maxCloudGap = canvasHeight / 2.6;
	var fairyWidth = canvasWidth / 13, fairyHeight = canvasHeight / 20, fairyGap, fairyTicker = 0, fairySpeed = canvasWidth / 600;
	var particleCount = 12, particleDir = canvasWidth / 800, particleOut = [[0, -2.2],[1, -2],[2, -1],[2.2, 0],[2, 1],[1, 2],[0, 2.2],[-1, 2],[-2, 1],[-2.2, 0],[-2, -1],[-1, -2]];
	var spawnCloud = false, spawnFairy = false, spawnFairyNext = false;
	var openingSeqDone = false, onMainMenu = false, openingStop = false, startedLoop = false, onChooseMenu = false, menuBGimg, onHighScoreMenu = false, onInstructionMenu = false, onTandCMenu = false;
	if(touchable){
		cloudHeight = canvasHeight / 12, fairyHeight = canvasHeight / 17, playerGrav = 0.425, cloudGap = canvasHeight / 7.3, maxCloudGap = canvasHeight / 2.2;
	}
	
	/**
	 * Get a random number between min and max values
	 * @param {number} Low end of the scale
	 * @param {number} High end of the scale
	 * @return {number} Random number between low and high
	 */
	function randomLowHigh(low, high){
		return Math.floor(Math.random() * (high-low + 1) + low);
	}
	
	/**
	 * Bind the window resize event for responsive
	 */
	window.onresize = function(e){
		//TODO : IMPLEMENT RESIZING GAME

	};
	
	/**
	 * Helper function for sorting an array by number
	 * @param {number} a First number to compare
	 * @param {number} b Second number to compare
	 * @return {number} a - b, used to see which number is higher for sorting purposes
	 */
	function sortNumber(a, b){
		return a - b;	
	}
	
	/**
	 * Helper function to get a random colour
	 * @return {Array}.<number> 3 numbers between 0 and 255 making up a random rgb colour
	 */
	function randRGB(){
		return [Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)];
	}
	
	/**
	 * Helper function to get random fairy dust color
	 * @return {Array}.<number> 3 numbers, first 2 are 252, last is between 0 and 170
	 */
	function randFairyDustRGB(){
		return [252, 252, Math.round((Math.random() * 85))];
	}
	
	
	
	/**
	 * Helper function to get a random shade of grey
	 * @return {Array}.<number> 3 numbers that are the same, making up a random shade of grey in RGB, number is between 0 and 255
	 */
	function randGreyRGB(){
		var rand = Math.round(Math.random() * 255);
		return rand;
	}
	
	/**
	 * Bound a number between a range
	 * @param {number} n Number to be bound
	 * @param {number} low Low end of the range
	 * @param {number} high High end of the range
	 * @return {number} The range bounded number
	 */
	function bound(n, low, high){
		return Math.max(Math.min(n, high), low);
	}
	
	/**
	 * Get a random cloud picture from a list
	 * @param {Array - img} list An array of pictures that can be used
	 * @return {img} The image to use
	 */
	function getRandomCloudPicture(list){
		var i = Math.floor(Math.random() * 13);
		return list[i];
	}
	
	/**
	 * Asset loading object
	 */
	var assetLoader = (function(){
		
		// images dictionary
		this.imgs = images;

		var assetsLoaded = 0;
		var numOfImgs = Object.keys(this.imgs).length;
		this.totalAssets = numOfImgs;
		
		/**
		 * Ensure assets are loaded before using them
		 * @param {assetLoader} _this Reference to the AssetLoader Object
		 * @param {string} dic Dictionary name
		 * @param {string} name Asset name
		 */
		function assetLoaded(_this, dic, name){
			if(_this[dic][name].status === "loaded"){
				return;
			}
			_this[dic][name].status = "loaded";
			assetsLoaded ++;
			loadingProgress(assetsLoaded, _this.totalAssets);

			// check if all assets are loaded
			if(assetsLoaded === _this.totalAssets){
				startOpeningSeq();
			}
		}
		// create assets, set callback, set source
		var _this = this;
		var src = '';

		src = this.imgs.bg;
		this.imgs.bg = new Image();
		this.imgs.bg.status = "loading";
		this.imgs.bg.name = "bg";
		this.imgs.bg.onload = function(){
			assetLoaded(_this, "imgs", "bg");	
		};
		this.imgs.bg.src = src;
		
		src = this.imgs.fairy;
		this.imgs.fairy = new Image();
		this.imgs.fairy.status = "loading";
		this.imgs.fairy.name = "fairy";
		this.imgs.fairy.onload = function(){
			assetLoaded(_this, "imgs", "fairy");	
		};
		this.imgs.fairy.src = src;
		
		src = this.imgs.percy;
		this.imgs.percy = new Image();
		this.imgs.percy.status = "loading";
		this.imgs.percy.name = "percy";
		this.imgs.percy.onload = function(){
			assetLoaded(_this, "imgs", "percy");	
		};
		this.imgs.percy.src = src;
		
		src = this.imgs.percy2;
		this.imgs.percy2 = new Image();
		this.imgs.percy2.status = "loading";
		this.imgs.percy2.name = "percy2";
		this.imgs.percy2.onload = function(){
			assetLoaded(_this, "imgs", "percy2");	
		};
		this.imgs.percy2.src = src;
		
		src = this.imgs.choosebg;
		this.imgs.choosebg = new Image();
		this.imgs.choosebg.status = "loading";
		this.imgs.choosebg.name = "choosebg";
		this.imgs.choosebg.onload = function(){
			assetLoaded(_this, "imgs", "choosebg");	
		};
		this.imgs.choosebg.src = src;
		
		src = this.imgs.cloud;
		this.imgs.cloud = new Image();
		this.imgs.cloud.status = "loading";
		this.imgs.cloud.name = "cloud";
		this.imgs.cloud.onload = function(){
			assetLoaded(_this, "imgs", "cloud");	
		};
		this.imgs.cloud.src = src;
		
		src = this.imgs.open;
		this.imgs.open = new Image();
		this.imgs.open.status = "loading";
		this.imgs.open.name = "open";
		this.imgs.open.onload = function(){
			assetLoaded(_this, "imgs", "open");	
		};
		this.imgs.open.src = src;
		
		src = this.imgs.cloud1;
		this.imgs.cloud1 = new Image();
		this.imgs.cloud1.status = "loading";
		this.imgs.cloud1.name = "cloud1";
		this.imgs.cloud1.onload = function(){
			assetLoaded(_this, "imgs", "cloud1");	
		};
		this.imgs.cloud1.src = src;
		
		src = this.imgs.cloud2;
		this.imgs.cloud2 = new Image();
		this.imgs.cloud2.status = "loading";
		this.imgs.cloud2.name = "cloud2";
		this.imgs.cloud2.onload = function(){
			assetLoaded(_this, "imgs", "cloud2");	
		};
		this.imgs.cloud2.src = src;
		
		src = this.imgs.cloud3;
		this.imgs.cloud3 = new Image();
		this.imgs.cloud3.status = "loading";
		this.imgs.cloud3.name = "cloud3";
		this.imgs.cloud3.onload = function(){
			assetLoaded(_this, "imgs", "cloud3");	
		};
		this.imgs.cloud3.src = src;
		
		src = this.imgs.cloud4;
		this.imgs.cloud4 = new Image();
		this.imgs.cloud4.status = "loading";
		this.imgs.cloud4.name = "cloud4";
		this.imgs.cloud4.onload = function(){
			assetLoaded(_this, "imgs", "cloud4");	
		};
		this.imgs.cloud4.src = src;
		
		src = this.imgs.cloud5;
		this.imgs.cloud5 = new Image();
		this.imgs.cloud5.status = "loading";
		this.imgs.cloud5.name = "cloud5";
		this.imgs.cloud5.onload = function(){
			assetLoaded(_this, "imgs", "cloud5");	
		};
		this.imgs.cloud5.src = src;
		
		src = this.imgs.cloud6;
		this.imgs.cloud6 = new Image();
		this.imgs.cloud6.status = "loading";
		this.imgs.cloud6.name = "cloud6";
		this.imgs.cloud6.onload = function(){
			assetLoaded(_this, "imgs", "cloud6");	
		};
		this.imgs.cloud6.src = src;
		
		src = this.imgs.cloud7;
		this.imgs.cloud7 = new Image();
		this.imgs.cloud7.status = "loading";
		this.imgs.cloud7.name = "cloud7";
		this.imgs.cloud7.onload = function(){
			assetLoaded(_this, "imgs", "cloud7");	
		};
		this.imgs.cloud7.src = src;
		
		src = this.imgs.cloud8;
		this.imgs.cloud8 = new Image();
		this.imgs.cloud8.status = "loading";
		this.imgs.cloud8.name = "cloud8";
		this.imgs.cloud8.onload = function(){
			assetLoaded(_this, "imgs", "cloud8");	
		};
		this.imgs.cloud8.src = src;
		
		src = this.imgs.cloud9;
		this.imgs.cloud9 = new Image();
		this.imgs.cloud9.status = "loading";
		this.imgs.cloud9.name = "cloud9";
		this.imgs.cloud9.onload = function(){
			assetLoaded(_this, "imgs", "cloud9");	
		};
		this.imgs.cloud9.src = src;
		
		src = this.imgs.cloud10;
		this.imgs.cloud10 = new Image();
		this.imgs.cloud10.status = "loading";
		this.imgs.cloud10.name = "cloud10";
		this.imgs.cloud10.onload = function(){
			assetLoaded(_this, "imgs", "cloud10");	
		};
		this.imgs.cloud10.src = src;
		
		src = this.imgs.cloud11;
		this.imgs.cloud11 = new Image();
		this.imgs.cloud11.status = "loading";
		this.imgs.cloud11.name = "cloud11";
		this.imgs.cloud11.onload = function(){
			assetLoaded(_this, "imgs", "cloud11");	
		};
		this.imgs.cloud11.src = src;
		
		src = this.imgs.cloud12;
		this.imgs.cloud12 = new Image();
		this.imgs.cloud12.status = "loading";
		this.imgs.cloud12.name = "cloud12";
		this.imgs.cloud12.onload = function(){
			assetLoaded(_this, "imgs", "cloud12");	
		};
		this.imgs.cloud12.src = src;
		
		src = this.imgs.cloud13;
		this.imgs.cloud13 = new Image();
		this.imgs.cloud13.status = "loading";
		this.imgs.cloud13.name = "cloud13";
		this.imgs.cloud13.onload = function(){
			assetLoaded(_this, "imgs", "cloud13");
		};
		this.imgs.cloud13.src = src;
		
		src = this.imgs.present;
		this.imgs.present = new Image();
		this.imgs.present.status = "loading";
		this.imgs.present.name = "cloud13";
		this.imgs.present.onload = function(){
			assetLoaded(_this, "imgs", "present");
		};
		this.imgs.present.src = src;
		
		src = this.imgs.repeat;
		this.imgs.repeat = new Image();
		this.imgs.repeat.status = "loading";
		this.imgs.repeat.name = "repeat";
		this.imgs.repeat.onload = function(){
			assetLoaded(_this, "imgs", "repeat");
		};
		this.imgs.repeat.src = src;
		
		src = this.imgs.multi;
		this.imgs.multi = new Image();
		this.imgs.multi.status = "loading";
		this.imgs.multi.name = "multi";
		this.imgs.multi.onload = function(){
			assetLoaded(_this, "imgs", "multi");
		};
		this.imgs.multi.src = src;
		
		src = this.imgs.instructionbg;
		this.imgs.instructionbg = new Image();
		this.imgs.instructionbg.status = "loading";
		this.imgs.instructionbg.name = "instructionbg";
		this.imgs.instructionbg.onload = function(){
			assetLoaded(_this, "imgs", "instructionbg");
		};
		this.imgs.instructionbg.src = src;
		
		src = this.imgs.highScorebg;
		this.imgs.highScorebg = new Image();
		this.imgs.highScorebg.status = "loading";
		this.imgs.highScorebg.name = "highScorebg";
		this.imgs.highScorebg.onload = function(){
			assetLoaded(_this, "imgs", "highScorebg");
		};
		this.imgs.highScorebg.src = src;
		
		src = this.imgs.tAndCbg;
		this.imgs.tAndCbg = new Image();
		this.imgs.tAndCbg.status = "loading";
		this.imgs.tAndCbg.name = "tAndCbg";
		this.imgs.tAndCbg.onload = function(){
			assetLoaded(_this, "imgs", "tAndCbg");
		};
		this.imgs.tAndCbg.src = src;
		
		src = this.imgs.instructionbgie;
		this.imgs.instructionbgie = new Image();
		this.imgs.instructionbgie.status = "loading";
		this.imgs.instructionbgie.name = "instructionbgie";
		this.imgs.instructionbgie.onload = function(){
			assetLoaded(_this, "imgs", "instructionbgie");
		};
		this.imgs.instructionbgie.src = src;
		
		src = this.imgs.highScorebgie;
		this.imgs.highScorebgie = new Image();
		this.imgs.highScorebgie.status = "loading";
		this.imgs.highScorebgie.name = "highScorebgie";
		this.imgs.highScorebgie.onload = function(){
			assetLoaded(_this, "imgs", "highScorebgie");
		};
		this.imgs.highScorebgie.src = src;
		
		src = this.imgs.choosebgie;
		this.imgs.choosebgie = new Image();
		this.imgs.choosebgie.status = "loading";
		this.imgs.choosebgie.name = "choosebgie";
		this.imgs.choosebgie.onload = function(){
			assetLoaded(_this, "imgs", "choosebgie");	
		};
		this.imgs.choosebgie.src = src;
		
		src = this.imgs.openie;
		this.imgs.openie = new Image();
		this.imgs.openie.status = "loading";
		this.imgs.openie.name = "open";
		this.imgs.openie.onload = function(){
			assetLoaded(_this, "imgs", "openie");	
		};
		this.imgs.openie.src = src;
		
		return{
			imgs: this.imgs,
			totalAssets: this.totalAssets
		};
	})();
	
	/**
	 * A Sprite Sheet Class
	 * @constructor
	 * @param {string} path Image path to use
	 * @param {number} frameWidth Width (px) of each frame
	 * @param {number} frameHeight (px) of each frame
	 * @param {number} objectWidth Width to draw the object
	 * @param {number} objectHeight Height to draw the object
	 */
	function SpriteSheet(path, frameWidth, frameHeight, objectWidth, objectHeight){
		this.image = new Image();
		this.frameWidth = frameWidth;
		this.frameHeight = frameHeight;
		this.objectWidth = objectWidth;
		this.objectHeight = objectHeight;
				
		// calculate the number of frames in a row
		var _this = this;
		this.image.onload = function(){
			_this.framesPerRow = Math.floor(_this.image.width / _this.frameWidth);
		};
		this.image.src = path;
	}
	
	/**
	 * An Animation Class
	 * @constructor
	 * @param {SpriteSheet} spritesheet The SpriteSheet to use
	 * @param {number} framespeed Frames to wait before transitioning the animation
	 * @param {number} startFrame The Starting frame of the animation
	 * @param {number} endFrame The ending frame of the animation
	 */
	function Animation(spritesheet, framespeed, startFrame, endFrame){
		var animationSequence = [];
		var currentFrame = 0;
		var counter = 0;
		
		// start and end ranges
		for(var frameNumber = startFrame; frameNumber <= endFrame; frameNumber++){
			animationSequence.push(frameNumber);
		}		
		
		
		/**
		* The update function for the animation
		*/
		this.update = function(){
			
			// update the frame if it is the right time
			if(counter == (framespeed - 1)){
				currentFrame = (currentFrame + 1) % animationSequence.length;
			}
			
			// update the counter
			counter = (counter + 1) % framespeed;
		};
		
		/**
		* The draw function for the animation
		* @param {number} x The 'x' position to draw
		* @param {number} y The 'y' position to draw
		*/
		this.draw = function(x, y){
			
			//get the row/ column of the frame
			var row = Math.floor(animationSequence[currentFrame] / spritesheet.framesPerRow);
			var column = Math.floor(animationSequence[currentFrame] % spritesheet.framesPerRow);

			ctx.drawImage(spritesheet.image, (column * spritesheet.frameWidth), (row * spritesheet.frameHeight), spritesheet.frameWidth, spritesheet.frameHeight, x, y, spritesheet.objectWidth, spritesheet.objectHeight);
			 
		};
	}
	
	/**
	* Vector Class, each object will inherit this class to provide Vector functionality
	* @interface
	* @param {number} x Center 'x' Co-ord
	* @param {number} y Center 'y' Co-ord
	* @param {number} dx 'x' velocity
	* @param {number} dy 'y' velocity
	*/
	function Vector(x, y, dx, dy){
		
		// position
		this.x = x || 0;
		this.y = y || 0;	
		
		// velocity
		this.dx = dx || 0;
		this.dy = dy || 0;
		
		/**
		* Move the vector position co-ords by the velocity of the object
		*/
		this.move = function(){
			this.x += this.dx;
			this.y += this.dy;
		};
		
		/**
		* Get the minimum distance between two vectors
		* @param {Vector} vector The vector to measure against
		* @return {number} Minimum Distance
		*/
		this.getMinDist = function(vector){
			var minDist = Infinity;
			var max = Math.max(Math.abs(this.dx), Math.abs(this.dy), Math.abs(vector.dx), Math.abs(vector.dy));
			var slice = 1 / max;
			var x, y, distSquared;
			
			// get the center of each vector
			var vector1 = {};
			var vector2 = {};
			vector1.x = this.x + this.width / 2;
			vector1.y = this.y + this.height / 2;
			vector2.x = vector.x + vector.width / 2;
			vector2.y = vector.y + vector.height / 2;
			
			for(var percent = 0; percent < 1; percent += slice){
				x = (vector1.x + this.dx * percent) - (vector2.x + vector.dx * percent);
				y = (vector1.y + this.dy * percent) - (vector2.y + vector.dy * percent);
				
				// pythagoras theorum to get distance between two points
				distSquared = (x * x) + (y * y);
				minDist = Math.min(minDist, distSquared);
			}
			return Math.sqrt(minDist);
		};
	}
	
	/**
	 * The mouse singleton object to store values of x and y mouse co-ords
	 */
	mouse = (function(mouse){
		this.x = 0;
		this.y = 0;
		return this;
	})();
	
	/**
	 * The player object
	 * @extends {Vector}
	 */
	player = (function(player){
		
		// player properties
		player.width = canvasWidth / 13;
		player.height = canvasHeight / 6.5;
		player.speed = 0;
		player.prevX = playerStartX;
		player.prevY = playerStartY;
		player.dir = "right";
		player.dustCounter = 0;
		
		// jumping
		/** @const */ player.gravity = playerGrav;
		player.jumpDy = -(canvasHeight / 45);
		player.startedGame = false;
		player.isGrounded = true;
		player.bounce = false;
		player.isHigh = false;
		player.isFallling = false;

		// sprite sheet control
		player.sheet = new SpriteSheet(assetLoader.imgs.percy.src, 160.333, 280, player.width, player.height);
		player.walkLeftAnim = new Animation(player.sheet, 4, 6, 11);
		player.walkRightAnim = new Animation(player.sheet, 4, 0, 5);
		player.anim = player.walkRightAnim;
		
		Vector.call(player, playerStartX, playerStartY, 0, 0);
		
		/**
		 * Update the player  position and the state of
		 *	the player's animation cycle		
		 */
		player.update = function(){
			
			// lateral movement classifier
			if(mouse.x < player.x && mouse.x !== 0){
				player.dir = "left";
			}else if(mouse.x > player.x){
				player.dir = "right";
			}
			
			//control floor movement
			if(player.isGrounded){
				if(player.dir === "left"){
					player.dx = player.x - maxGroundSpeed > mouse.x ? -(maxGroundSpeed) : -(player.x - mouse.x);
					player.anim = player.walkLeftAnim;
				}else{
					player.dx = player.x + maxGroundSpeed > mouse.x ? mouse.x - player.x : maxGroundSpeed;
					player.anim = player.walkRightAnim;
				}
				if(mouse.x === player.prevX){
					player.dx = 0;
					player.anim = player.dir === "left" ? player.walkLeftAnim : player.walkRightAnim;
				}
				
				// jump control for the first jump
				if((KEY_STATUS.space || clickJump) && player.dy === 0 && player.isGrounded){
					player.isGrounded = false;
					clickJump = false;
					player.dy = player.jumpDy;
				}
				
			// control first jump
			}else if(!player.startedGame){
			
				// check if we land after first jump
				if(player.y >= playerStartY){
					player.y = playerStartY;
					player.dy = 0;
					player.isGrounded = true;
					player.isFalling = false;
				}
				
				// control lateral
				if(player.dir === "left"){
					player.dx = player.x - maxJumpSpeed > mouse.x ? -(maxJumpSpeed) : -(player.x - mouse.x);
					if(player.dy > 0){
						player.isFalling = true;
						player.anim = player.walkLeftAnim;
					}else{
						player.anim = player.walkLeftAnim;
					}
				}else{
					player.dx = player.x + maxJumpSpeed > mouse.x ? mouse.x - player.x : maxJumpSpeed;
					if(player.dy > 0){
						player.isFalling = true;
						player.anim = player.walkRightAnim;
					}else{
						player.anim = player.walkRightAnim;
					}
				}
				
				// jump control for the first jump
				if((KEY_STATUS.space || clickJump) && player.dy === 0 && player.isGrounded){
					player.isGrounded = false;
					clickJump = false;
					player.dy = player.jumpDy;
				}
				
			// control game
			}else{
				
				// check if we should bounce the player
				if(player.bounce){
					player.bounce = false;
					player.dy = player.jumpDy;
				}
				
				// control lateral
				if(player.dir === "left"){
					player.dx = player.x - maxJumpSpeed > mouse.x ? -(maxJumpSpeed) : -(player.x - mouse.x);
					if(player.dy > 0){
						player.isFalling = true;
						player.anim = player.walkLeftAnim;
					}else{
						player.isFalling = false;
						player.anim = player.walkLeftAnim;
					}
				}else{
					player.dx = player.x + maxJumpSpeed > mouse.x ? mouse.x - player.x : maxJumpSpeed;
					if(player.dy > 0){
						player.isFalling = true;
						player.anim = player.walkRightAnim;
					}else{
						player.isFalling = false;
						player.anim = player.walkRightAnim;
					}
				}
			}
			
			if(player.y < canvasHeight / 5 && !player.isFalling){
				player.isHigh = true;	
			}else{
				player.isHigh = false;
			}
			
			this.move();
			
			if(player.isHigh){
				player.y = player.prevY;
			}
			
			// check for if player is at the edge of the canvas
			if(player.x > (canvasWidth - player.width)){
				player.x = (canvasWidth - player.width);
			}else if(player.x < 0){
				player.x = 0;
			}
			
			// factor in gravity
			if(!player.isGrounded){
				player.dy += player.gravity;	
			}
			
			player.prevX = player.x;
			player.prevY = player.y;
			player.anim.update();
			if(!player.isGrounded && player.dustCounter === 0){
				for(var i = 0; i < 4; i++){
					dust.push(new FairyDust(player.x + (Math.random() * (player.width / 2)), player.y + (Math.random() * (player.height / 2)), 1.3, 0, 3));
				}
			}
			player.dustCounter = (player.dustCounter + 1) % 2;
		};

		
		/**
		 * Handle drawing the player
		 */
		player.draw = function(){
			player.anim.draw(player.x, player.y);	
		};
		
		/**
		 * Reset the player state for a new game
		 * @param {String} fairy Which fairy has been chosen (magic or sparkle) 
		 */
		player.reset = function(fairy){
			player.dustCounter = 0;
			player.width = canvasWidth / 15;
			player.height = canvasHeight / 8;
			player.speed = 0;
			player.prevX = playerStartX;
			player.prevY = playerStartY;
			player.dir = "right";
			player.jumpDy = -(canvasHeight / 45);
			player.startedGame = false;
			player.isGrounded = true;
			player.bounce = false;
			player.isHigh = false;
			player.sheet = new SpriteSheet(fairy === "magic" ? assetLoader.imgs.percy.src : assetLoader.imgs.percy2.src, 160.333, 280, player.width, player.height);
			player.walkLeftAnim = new Animation(player.sheet, 4, 6, 11);
			player.walkRightAnim = new Animation(player.sheet, 4, 0, 5);
			player.anim = player.walkRightAnim;
			Vector.call(player, playerStartX, playerStartY, 0, 0);
		};
		
		return player;
		
	})(Object.create(Vector.prototype));
	
	/**
	 * Key Code and Status handling
	 */
	var KEY_CODES = {
		37 : 'left',
		39 : 'right'
	};
	var KEY_STATUS = {};
	// set default value of codes we are listening for to false
	for(var code in KEY_CODES){
		if(KEY_CODES.hasOwnProperty(code)){
			KEY_STATUS[KEY_CODES[code]] = false;	
		}
	}
	
	/**
	 * Bind Mouse/ Key Events
	 * Touch Events for a touch screen device
	 */
	if(!touchable){
		document.onkeydown = function(e){
			var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
			if(KEY_CODES[keyCode]){
				KEY_STATUS[KEY_CODES[keyCode]] = true;
			}
		};
		document.onkeyup = function(e){
			var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
			if(KEY_CODES[keyCode]){
				e.preventDefault();
				KEY_STATUS[KEY_CODES[keyCode]] = false;
			}
		};
		
		document.onmousemove = function(e){
			var offset = canvas.getBoundingClientRect();
			if(e.clientX - offset.left > 0 && e.clientX - offset.left < canvasWidth){
				mouse.x = e.clientX - offset.left;
			}	
		};
		document.onmousedown = function(e){
			var offset = canvas.getBoundingClientRect();
			if(e.clientX - offset.left > 0 && e.clientX - offset.left < canvasWidth && player.isGrounded && !onChooseMenu){
				clickJump = true;
			}
			if(onChooseMenu){
				if(e.clientX - offset.left > 209 && e.clientX - offset.left < 437 && e.clientY - offset.top > 150 && e.clientY - offset.top < 568){
					clickChoose = true;
					chosenFairy = "magic";
				}
				if(e.clientX - offset.left > 525 && e.clientX - offset.left < 760 && e.clientY - offset.top > 150 && e.clientY - offset.top < 568){
					clickChoose = true;
					chosenFairy = "sparkle";
				}
			}
		};
	}else{
		canvas.addEventListener('touchstart', onTouchStart, false);
		canvas.addEventListener('touchmove', onTouchMove, false);
		canvas.addEventListener('touchend', onTouchEnd, false);
	}
	
	function onTouchStart(e){
		var offset = canvas.getBoundingClientRect();
		if(e.touches[0].clientX - offset.left > 0 && e.touches[0].clientX - offset.left < canvasWidth && player.isGrounded && !onChooseMenu){
			clickJump = true;
		}
		if(onChooseMenu){
			if(e.touches[0].clientX - offset.left > 209 && e.touches[0].clientX - offset.left < 437 && e.touches[0].clientY - offset.top > 150 && e.touches[0].clientY - offset.top < 568){
				clickChoose = true;
				chosenFairy = "magic";
			}
			if(e.touches[0].clientX - offset.left > 525 && e.touches[0].clientX - offset.left < 760 && e.touches[0].clientY - offset.top > 150 && e.touches[0].clientY - offset.top < 568){
				clickChoose = true;
				chosenFairy = "sparkle";
			}
		}
	}
	
	function onTouchEnd(e){	
	}
	
	function onTouchMove(e){
		e.preventDefault();
		var offset = canvas.getBoundingClientRect();
		mouse.x = e.touches[0].clientX - offset.left;
	}
	
	/**
	 * Class representing areas of the background that will repeat
	 */
	function BackgroundRepeat(x, y){
		this.img = assetLoader.imgs.repeat;
		this.x = x;
		this.y = y;
		this.width = canvasWidth;
		this.height = canvasHeight;
		this.dx = 0;
		this.dy = 0;
		
		this.update = function(){
			if(player.isHigh){
				this.dy =  -(player.dy);	
			}else{
				this.dy = 0;	
			}

			this.y += this.dy;
		};
		
		this.draw = function(){
			ctx.drawImage(this.img, 0, this.y, this.width, this.height);
		};
	}
	
	/**
	 * Background Singleton Object
	 */
	var background = (function(){
		this.x = 0;
		this.y = 0;
		this.width = canvasWidth;
		this.height = canvasHeight;
		this.dy = 0;
		this.dx = 0;
		this.repeating = false;
		this.repeats = [new BackgroundRepeat(0, -canvasHeight * 2), new BackgroundRepeat(0, -canvasHeight), new BackgroundRepeat(0, 0)];
		
		/**
		 * Update the Background
		 */
		this.update = function(){

			if(player.startedGame){
				if(player.isHigh){
					this.dy = -(player.dy);	
 				}else{
					this.dy = 0;	
				}
			}
			this.y += this.dy;
			if(!this.repeating && this.y > assetLoader.imgs.bg.height - canvasHeight){
				this.repeating = true;
			}
			if(this.repeating){
				for(var i = 0; i < repeats.length; i++){
					repeats[i].update();
				}
				for(var i = 0; i < repeats.length; i++){
					if(repeats[i].y >= canvasHeight){
						i !== 0 ? repeats[i].y = repeats[i - 1].y - canvasHeight : repeats[i].y = repeats[2].y - canvasHeight;
					}
				}
			}
		};
		
		/**
		 * Draw the Background
		 */
		this.draw = function(){
			ctx.fillStyle = "#000000";
			ctx.fillRect(0, 0, canvasWidth, canvasHeight);
			if(this.repeating){
				for(var i = 0; i < repeats.length; i++){
					repeats[i].draw();
				}
			}
			ctx.drawImage(assetLoader.imgs.bg, 0, -(assetLoader.imgs.bg.height - canvasHeight) + this.y, canvasWidth, assetLoader.imgs.bg.height);
		};
		
		/**
		 * Reset background to zero
		 */
		this.reset = function(){
			this.x = 0;
			this.y = 0;
			this.dx = 0;
			this.dy = 0;
			this.repeating = false;
			this.repeats = [new BackgroundRepeat(0, -canvasHeight * 2), new BackgroundRepeat(0, -canvasHeight), new BackgroundRepeat(0, 0)];

		};
		
		return{
			update : this.update,
			draw : this.draw,
			reset : this.reset
		};
	})();
	
	/**
	 * Fairy Dust Class to create bits of fairy dust
	 * @param {number} x Starting 'x' position
	 * @param {number} y Starting 'y' position
	 * @param {number} r Radius of the dust
	 * @param {number} dx The 'x' velocity of the Dust
	 * @param {number} dy The 'y' velocity of the Dust
	 */
	function FairyDust(x, y, r, dx, dy){
		this.x = x;
		this.y = y;
		this.dx = dx;
		this.dy = dy;
		this.radius = r;
		this.color = randFairyDustRGB();
		this.counter = 0;
		this.life = Math.random() * 15 + 12;
		this.end = false;
		
		this.update = function(){
			this.x += this.dx;
			if(player.isHigh){
				this.y += (this.dy + -(player.dy));
			}else{
				this.y += this.dy;
			}
			this.counter += 1;
			if(this.counter >= this.life){
				this.end = true;
			}
		};
		this.draw = function(){
			ctx.fillStyle = "rgb(" + this.color[0] + "," + this.color[1] + "," + this.color[2] + ")";
			ctx.beginPath();
			ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
			ctx.fill();
		};
	}
	
	/**
	 * Snowflake class to create snow particles
	 * @param {number} x Starting 'x' position
	 * @param {number} y Starting 'y' position
	 * @param {number} radius Radius of the Snoflake
	 * @param {number} dx The 'x' velocity of the Snowflake
	 * @param {number} dy The 'y' velocity of the Snowflake
	 */
	function Snowflake(x, y, radius, dx, dy){
				
		this.x = x;
		this.y = y;
		this.radius = radius;
		this.dx = dx;
		this.dy = dy;
		
		this.update = function(){
			this.x += this.dx;
			
			// move the y position of snow based on player
			if(player.isHigh){
				this.y += (this.dy + -(player.dy));
			}else{
				this.y += this.dy;
			}
		};
		
		this.draw = function(){
			ctx.fillStyle = "white";
			ctx.beginPath();
			ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
			ctx.fill();
		};
	}
	
	/**
	 * Double class
	 * @constructor
	 * @extends {Vector}
	 * @param {number} x Starting 'x' position
	 * @param {number} y Starting 'y' position
	 * @param {String} num The number to show, with or without 'x' added
	 */
	function Double(x, y, num){
		this.x = x;
		this.y = y;
		this.width = cloudWidth;
		this.height = cloudHeight;
		this.opacity = 1;
		this.num = num;
		Vector.call(this, this.x, this.y, 0, 0);
		this.opacityTicker = 1;
		
		/**
		 * Update present position and opacity
		 */
		this.update = function(){
			this.opacityTicker = (this.opacityTicker + 1) % 8;
			if(player.startedGame){
				if(player.isHigh){
					this.dy = -(player.dy);	
				}else{
					this.dy = cloudSpeed;	
				}
			}else{
				this.dy = cloudSpeed;
			}
			
			if(this.opacityTicker === 0){
				this.opacity -= 0.1;
			}
			this.move();
			
		};
		
		this.draw = function(){
			ctx.globalAlpha = this.opacity;
			ctx.fillStyle = "#FFFB00";
			ctx.font = "20px Arial";
			ctx.fillText(this.num, this.x, this.y);
			ctx.globalAlpha = 1.0;
		};
	}
	Present.prototype = Object.create(Vector.prototype);
	
	/**
	 * Present class
	 * @constructor
	 * @extends {Vector}
	 * @param {number} x Starting 'x' position of the present
	 * @param {number} y Starting 'y' position of the present
	 */
	function Present(x, y){
		this.x = x;
		this.y = y;
		this.width = cloudWidth;
		this.height = cloudHeight;
		this.opacity = 1;
		this.img = getRandomCloudPicture(cloudList);
		Vector.call(this, this.x, this.y, 0, 0);
		this.opacityTicker = 1;
		
		/**
		 * Update present position and opacity
		 */
		this.update = function(){
			this.opacityTicker = (this.opacityTicker + 1) % 8;
			if(player.startedGame){
				if(player.isHigh){
					this.dy = -(player.dy);	
				}else{
					this.dy = cloudSpeed;	
				}
			}else{
				this.dy = cloudSpeed;
			}
			
			if(this.opacityTicker === 0){
				this.opacity -= 0.1;
			}
			this.move();
			
		};
		
		this.draw = function(){
			ctx.globalAlpha = this.opacity;
			ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
			ctx.globalAlpha = 1.0;
		};
	}
	Present.prototype = Object.create(Vector.prototype);
	
	/**
	 * Cloud class
	 * @constructor
	 * @extends {Vector}
	 * @param {number} x Starting 'x' position of the cloud
	 * @param {number} y Starting 'y' position of the cloud
	 */
	function Cloud(x, y){
		this.x = x;
		this.y = y;
		this.width = cloudWidth;
		this.height = cloudHeight;
		this.opacity = 1.0;
		this.img = assetLoader.imgs.present;
		Vector.call(this, this.x, this.y, 0, 0);
		this.translate = Math.random() < 0.5 ? 0 : Math.PI;
		this.translateInc = Math.PI / 50;
		
		/**
		 * Update the clouds position
		 */
		this.update = function(){
			
			// check if the player has started the game, move clouds accordingly
			if(player.startedGame){
				if(player.isHigh){
					this.dy = -(player.dy);	
				}else{
					this.dy = cloudSpeed;	
				}
			}else{
				this.dy = cloudSpeed;
			}
			this.move();
			if(this.translate >= Math.PI * 2){
				this.translate = 0;
			}
			this.translate += this.translateInc;
		};
		
		/**
		 * Handle drawing the cloud
		 */
		this.draw = function(){
			ctx.save();
			ctx.translate(this.x + this.width / 2, this.y);
			ctx.rotate((Math.sin(this.translate) * Math.PI/180) * 7);
			ctx.globalAlpha = this.opacity;
			ctx.drawImage(this.img, 0 - this.width / 2, 0, this.width, this.height);
			ctx.globalAlpha = 1.0;
			ctx.restore();
		};
	}
	Cloud.prototype = Object.create(Vector.prototype);
	
	/**
	 * Fairy class
	 * @constructor
	 * @extends {Vector}
	 * @param {number} x Starting 'x' position of the fairy
	 * @param {number} y Starting 'y' position of the fairy
	 */
	function Fairy(x, y){
		this.x = x;
		this.y = y;
		this.width = cloudWidth;
		this.height = cloudHeight;
		this.dir = this.x < canvasWidth / 2 ? "right" : "left";
		
		this.dy = cloudSpeed;
		this.dx = this.dir = "right" ? fairySpeed : -(fairySpeed);
		
		Vector.call(this, this.x, this.y, this.dx, this.dy);
		
		/**
		 * Update the fairies' position
		 */
		this.update = function(){
			
			// check if the player has started the game, move fairies accordingly
			if(player.isHigh){
				this.dy = -(player.dy);	
			}else{
				this.dy = cloudSpeed;	
			}
			// check for if the fairie needs to turn
			if(this.x <= canvasWidth / 200){
				this.dir = "right";
				this.dx = fairySpeed;
				this.anim = this.flyRightAnim;
			}else if(this.x >= canvasWidth - (canvasWidth / 14)){
				this.dir = "left";
				this.dx = -(fairySpeed);
				this.anim = this.flyLeftAnim;
			}
			this.move();
		};
		
		/**
		 * Handle drawing the fairy
		 */
		this.draw = function(){
			ctx.drawImage(assetLoader.imgs.cloud, this.x, this.y, this.width, this.height);
		};
	}
	Fairy.prototype = Object.create(Vector.prototype);
	
	/**
	 * Particle class for collision particles
	 * @constructor
	 * @param {number} x Starting 'x' position of particle
	 * @param {number} y Starting 'y' position of particle
	 * @param {number} i Number 1-12 position of the particle, controls the flight path of the particle
	 */
	function Particle(x, y, i){
		this.x = x || 0;
		this.y = y || 0;
		this.radius = canvasWidth / 250;
		this.vx = particleOut[i][0];
		this.vy = particleOut[i][1];
		this.rgb = randGreyRGB();
		
		this.updateColor = function(){
			this.rgb = randGreyRGB();
		};
	}
	
	/**
	 * Draw any particles onto the screen
	 */
	function drawParticles(){
		for(var i = 0; i < particles.length; i++){
			var p = particles[i];
			ctx.beginPath();
			p.updateColor();
			ctx.fillStyle = "#76ED63";
			//ctx.fillStyle = "rgba(" + p.rgb + "," + p.rgb + "," + p.rgb + ", 1)";
			if(p.radius > 0){
				if(player.isHigh){
					p.y += -(player.dy);	
				}else{
					this.dy = cloudSpeed;	
				}
				ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2, false);
				ctx.fill();
				p.x += p.vx;
				p.y += p.vy;
				p.radius = Math.max(p.radius - 0.05, 0);
			}else{
				particles.splice(i, 1);
			}
		}
	}
	
	/**
	 * Opening Sequence Object
	 */
	var openingSeq = (function(){
		this.x = 0;
		this.y = 0;
		this.opacity = 0;
		this.width = canvasWidth;
		this.height = canvasHeight;
		this.counter = 0;
		this.update = function(){
			if(this.counter === 0){
				this.opacity += 0.01;
				if(this.opacity >= 0.9){
					this.opacity = 1;
					openingSeqDone = true;
					showMainMenu();
				}
			}
			this.counter = (this.counter + 1) % 2;
			
		};
		this.draw = function(){
			ctx.fillStyle = "black";
			ctx.globalAlpha = this.opacity;
			ctx.fillRect(0, 0, canvasWidth, canvasHeight);
			if(siteCode == "IE"){
				ctx.drawImage(assetLoader.imgs.openie, 0, 0, canvasWidth, canvasHeight);
			}else{
				ctx.drawImage(assetLoader.imgs.open, 0, 0, canvasWidth, canvasHeight);
			}
			ctx.globalAlpha = 1;
		};
		
		this.reset = function(){
			this.x = 0;
			this.y = 0;
			this.opacity = 0;
			this.width = canvasWidth;
			this.height = canvasHeight;
			this.counter = 0;
		};
		
		return{
			update : this.update,
			draw : this.draw,
			reset : this.reset
		};
	})();
	
	/**
	 * Instruction Menu Object
	 */
	var instructionMenu = (function(){
		this.x = 0;
		this.y = 0;
		this.width = canvasWidth;
		this.height = canvasHeight;
		this.update = function(){
			
		};
		this.draw = function(){
			if(siteCode == "IE"){
				ctx.drawImage(assetLoader.imgs.instructionbgie, 0, 0, canvasWidth, canvasHeight);
			}else{
				ctx.drawImage(assetLoader.imgs.instructionbg, 0, 0, canvasWidth, canvasHeight);
			}
		};
		
		return{
			update : this.update,
			draw : this.draw
		}
	})();
	
	/**
	 * High Score Menu Object
	 */
	var highScoreMenu = (function(){
		this.x = 0;
		this.y = 0;
		this.width = canvasWidth;
		this.height = canvasHeight;
		this.update = function(){
			
		};
		this.draw = function(){
			if(siteCode == "IE"){
				ctx.drawImage(assetLoader.imgs.highScorebgie, 0, 0, canvasWidth, canvasHeight);
			}else{
				ctx.drawImage(assetLoader.imgs.highScorebg, 0, 0, canvasWidth, canvasHeight);
			}
		};
		return{
			update : this.update,
			draw : this.draw
		}
	})();
	
	/**
	 * T and C's Menu Object
	 */
	var tAndCMenu = (function(){
		this.x = 0;
		this.y = 0;
		this.width = canvasWidth;
		this.height = canvasHeight;
		this.update = function(){
			
		};
		this.draw = function(){
			ctx.drawImage(assetLoader.imgs.tAndCbg, 0, 0, canvasWidth, canvasHeight);
		};
		return{
			update : this.update,
			draw : this.draw
		}
	})();
	
	/**
	 * Choose Menu Object
	 */
	var chooseMenu = (function(){
		this.x = 0;
		this.y = 0;
		this.width = canvasWidth;
		this.height = canvasHeight;
		this.update = function(){
			if(clickChoose){
				clickChoose = false;
				onChooseMenu = false;
				startGame(chosenFairy);
			}
		};
		this.draw = function(){
			if(siteCode === "IE"){
				ctx.drawImage(assetLoader.imgs.choosebgie, 0, 0, canvasWidth, canvasHeight);
			}else{
				ctx.drawImage(assetLoader.imgs.choosebg, 0, 0, canvasWidth, canvasHeight);
			}
		};
		this.reset = function(){
			this.x = 0;
			this.y = 0;
			this.width = canvasWidth;
			this.height = canvasHeight;
		};
		return{
			update : this.update,
			draw : this.draw,
			reset : this.reset
		};
	})();
	
	/**
	 * Handle spawning new objects such as clouds and fairies in game
	 */
	function spawnObjects(){
		if(spawnCloud){
			clouds.push(new Cloud((Math.random() * (canvasWidth - cloudWidth)), cloudStartY));
			spawnCloud = false;
		}
		if(spawnFairy){
			fairies.push(new Fairy(Math.random() < 0.5 ? canvasWidth / 10 : (canvasWidth / 10) * 9, cloudStartY));
			spawnFairy = false;
		}
	}
	
	/**
	 * Request animation polyfill - Allows for all browsers and falls back 
	 * 	to setTimout function if browser doesn't support it
	 */
	var requestAnimFrame = (function(){
		return window.requestAnimationFrame			||
				window.webkitRequestAnimationFrame 	||
				window.mozRequestAnimationFrame		||
				window.oRequestAnimationFrame		||
				window.msRequestAnimationFrame		||
				function(callback, element){
					window.setTimeout(callback, 1000/fps);
				};
	})();
	
	/**
	 * The player update function
	 */
	function playerUpdate(){
		player.update();
		player.draw();
		if(player.startedGame && canvasHeight - player.height < player.y){
			endGame();
		}
	}
	
	/**
	 * The cloud update function
	 */
	function cloudUpdate(){
		if(player.startedGame){
			cloudTicker = (cloudTicker + 1) % 1500;
		}
		var toSplice = [];
		if(clouds.length === 0){
			spawnCloud = true;
		}
		for(var i = 0; i < clouds.length; i++){
			
			// this update function controls whether or not to scroll the cloud based on player.dy
			clouds[i].update();
			clouds[i].draw();
			
			
			
			// check if we need to add a new cloud
			if(i === clouds.length - 1 && clouds[i].y >= (cloudStartY + cloudGap)){
			 	if(!spawnFairyNext){
					spawnCloud = true;
				}else{
					spawnFairy = true;
					spawnFairyNext = false;
				}
			}
			// check if to start fading cloud
			if(!player.startedGame && clouds[i].y > (canvasHeight / 3) * 2){
				clouds[i].opacity -= 0.025;	
			}
			
			// check if the cloud is too far off screen, or the cloud has faded
			if(clouds[i].y > canvasHeight + (canvasHeight / 8) || clouds[i].opacity <= 0.05){
				toSplice.push(i);
			}
			
			// check for cloud collision, if collision occurs, set bounce to true, add particles and add cloud to toSplice
			if(player.getMinDist(clouds[i]) <= Math.sqrt(Math.pow(clouds[i].height / 2, 2) + Math.pow(clouds[i].width / 2, 2))){
				player.bounce = true;
				player.startedGame = true;
				for(var j = 0; j < particleCount; j++){
					particles.push(new Particle(clouds[i].x + (clouds[i].width / 2), clouds[i].y + (clouds[i].height / 2), j));	
				}
				presents.push(new Present(clouds[i].x, clouds[i].y));
				toSplice.push(i);
				totalScore += scorePerBounce;
				if(prevScore - totalScore > scorePerBounce || scorePerBounce > prevScorePerBounce * 2){
				}
				prevScore = totalScore;
			}
		}
		
		if(cloudTicker === 0 && cloudGap < maxCloudGap){
			cloudGap += (canvasHeight / 100);
		}
		
		// sort the toSplice array
		toSplice.sort(sortNumber);
		
		// remove any clouds the need to be removed
		for(var i = toSplice.length - 1; i >= 0; i--){
			clouds.splice(toSplice[i], 1);	
		}
	}
	
	/**
	 * The fairy update function
	 */
	function fairyUpdate(){
		
		// check if we need to spawn a fairy
		if(fairyTicker === 800){
			spawnFairyNext = true;
			fairyTicker = 0;
		}
		var toSplice = [];
		for(var i = 0; i < fairies.length; i++){
			
			// update and draw functions for position
			fairies[i].update();
			fairies[i].draw();
			
			// check if fairy has left the bottom of the screen
			if(fairies[i].y > canvasHeight + (canvasHeight / 8)){
				toSplice.push(i);
			}
			
			// check fairy collisions, if collision occurs, set bounce to true, add partiles and add cloud to toSplice
			if(player.getMinDist(fairies[i]) <= Math.sqrt(Math.pow(fairies[i].height / 2 , 2) + Math.pow(fairies[i].width / 2, 2))){
				player.bounce = true;
				if(scorePerBounce >= 4000){
					totalScore += 12000;
					presents.push(new Double(fairies[i].x + (fairies[i].width / 2), fairies[i].y + (fairies[i].height /2), '12000!!!'));
					for(var j = 0; j < particleCount; j++){
						particles.push(new Particle(fairies[i].x + (fairies[i].width / 2), fairies[i].y + (fairies[i].height / 2), j));
					}
				}else{
					scorePerBounce += 1;
					prevScorePerBounce = scorePerBounce;
					for(var j = 0; j < particleCount; j++){
						particles.push(new Particle(fairies[i].x + (fairies[i].width / 2), fairies[i].y + (fairies[i].height / 2), j));
					}
					presents.push(new Double(fairies[i].x + (fairies[i].width / 2), fairies[i].y + (fairies[i].height /2), 'x' + scorePerBounce));
				}
				toSplice.push(i);
			}
		}

		// sort the toSplice array
		toSplice.sort(sortNumber);
		
		// remove any fairy that needs to be removed
		for(var i = toSplice.length - 1; i >= 0; i--){
			fairies.splice(toSplice[i], 1);	
		}
	}
	
	/**
	 * The background update function
	 */
	function backgroundUpdate(){
		background.update()
		background.draw();
		
		// update and draw snowflakes
		for(var i = snow.length; i < snowAmount; i++){
			snow.push(new Snowflake(randomLowHigh(canvasWidth / 10, canvasWidth * 0.9), randomLowHigh(-(canvasHeight) * 0.2, -(canvasHeight)) * 0.4, randomLowHigh(canvasWidth / 700, canvasWidth /550), randomLowHigh(-canvasWidth / 1000, canvasWidth / 1000), randomLowHigh(canvasHeight / 300, canvasHeight / 200)));
		}
		
		for(var i = 0; i < snow.length; i++){
			
			// check for snow leaving the screen, and recycle it if it is
			if(snow[i].x < 0 || snow[i].x > canvasWidth || snow[i].y > canvasHeight * 1.1){
				snow[i].x = randomLowHigh(canvasWidth / 10, canvasWidth * 0.9);
				snow[i].y = -(canvasHeight / 100);
			}
			snow[i].update();
			snow[i].draw();
		}
	}
	
	/**
	 * Present Update Function
	 */
	function presentUpdate(){
		var toSplice = [];
		for(var i = 0; i < presents.length; i++){
			presents[i].update();
			presents[i].draw();
			if(presents[i].opacity <= 0.1){
				toSplice.push(i);
			}
		}
		toSplice.sort(sortNumber);
		for(var i = toSplice.length - 1; i >= 0; i--){
			presents.splice(toSplice[i], 1);
		}
	}
	
	
	/**
	 * Scoreboard update and draw function
	 */
	function scoreboardUpdate(){
		ctx.strokeStyle = "black";
		ctx.lineWidth = "2px";
		ctx.fillStyle = "#E8FF54";
		ctx.textAlign = "right";
		ctx.font = "22px Arial";
		ctx.textBaseline = "middle";
		ctx.fillText(totalScore, canvasWidth * 0.8 + canvasWidth / 20, canvasHeight / 20 + canvasHeight / 40);
		ctx.fillStyle = "black";
		ctx.textAlign = "center";
		ctx.drawImage(assetLoader.imgs.multi, canvasWidth * 0.868 + canvasWidth / 20, canvasHeight / 33.5, canvasWidth / 15, canvasHeight / 12);
		if(scorePerBounce > 1000){
			ctx.font = "14px Arial";
		}
		if(scorePerBounce > 100000){
			ctx.font = "10px Arial";
		}
		ctx.fillText(scorePerBounce, canvasWidth * 0.9 + canvasWidth / 20, canvasHeight / 20 + canvasHeight / 40);
	}
	
	/**
	 * Function to update and draw dust
	 */
	function drawDust(){
		var toSplice = [];
		for(var i = 0; i < dust.length; i++){
			dust[i].update();
			dust[i].draw();
			if(dust[i].end){
				toSplice.push(i);
			}
		}
		toSplice.sort(sortNumber);
		for(var i = 0; i < toSplice.length; i++){
			dust.splice(toSplice[i], 1);
		}
	}
	
	/**
	 * The menu update function
	 */
	function updateMenu(){
		
	}
	
	/**
	 * The main game update function
	 */
	function updateGame(){
				
		// call each object type update function
		backgroundUpdate();
		scoreboardUpdate();
		drawDust();
		playerUpdate();
		cloudUpdate();
		fairyUpdate();
		presentUpdate();
		drawParticles();
	}
	
	/**
	 * The main game loop function
	 */
	function mainLoop(){
		requestAnimFrame(mainLoop);
		
		if(onChooseMenu){
			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
			
			//update
			updateChoose();
		}else if(onInstructionMenu){
			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
			updateInstructionMenu();
		}else if(onHighScoreMenu){
			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
			updateHighScoreMenu();
		}else if(onTandCMenu){
			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
			updateTandCMenu();
		}else if(!openingStop || onMainMenu){
			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
			
			// update and draw
			updateOpening();
		}
		if(!stop && !onChooseMenu && !onInstructionMenu && !onHighScoreMenu && !onTandCMenu){

			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
			
			// update 
			updateGame();

			// spawn objects			
			spawnObjects();
			if(player.startedGame){
				fairyTicker ++;	
			}
		}else{
			
		}
		
	}
	
	/**
	 * Start the game with new score and variables reset
	 * @param {String} fairy The fairy chosen (magic or sparkle)
	 */
	function startGame(fairy){
		$('.gameOver').css('display','none');
		prevScore = 0;
		totalScore = 0;
		spawnFairy = false;
		scorePerBounce = 1;
		$('#canvas').css('opacity','1');
		prevScore = 0;
		prevScorePerBounce = 1;
		cloudGap = canvasHeight / 9;
		cloudTicker = 1;
		fairyTicker = 1;
		fairies = [];
		clouds = [];
		presents = [];
		dust = [];
		spawnFairy = false;
		spawnFairyNext = false;
		spawnCloud = true;
		player.reset(fairy);
		background.reset();
		cloudList = [assetLoader.imgs.cloud1, 
		             assetLoader.imgs.cloud2,
		             assetLoader.imgs.cloud3,
		             assetLoader.imgs.cloud4,
		             assetLoader.imgs.cloud5,
		             assetLoader.imgs.cloud6,
		             assetLoader.imgs.cloud7,
		             assetLoader.imgs.cloud8,
		             assetLoader.imgs.cloud9,
		             assetLoader.imgs.cloud10,
		             assetLoader.imgs.cloud11,
		             assetLoader.imgs.cloud12,
		             assetLoader.imgs.cloud13
		             ];
		stop = false;
	}
	
	/**
	 * Choose Screen Update
	 */
	function updateChoose(){
		chooseMenu.update();
		chooseMenu.draw();
	}
	
	/**
	 * Instruction Screen Update
	 */
	function updateInstructionMenu(){
		instructionMenu.update();
		instructionMenu.draw();
	}
	
	function updateHighScoreMenu(){
		highScoreMenu.update();
		highScoreMenu.draw();
	}
	
	function updateTandCMenu(){
		tAndCMenu.update();
		tAndCMenu.draw();
	}
	
	/**
	 * Opening Seqence Update function
	 */
	function updateOpening(){
		if(!openingSeqDone){		
			openingSeq.update();
			openingSeq.draw();
		}else{
			openingSeq.draw();
		}
	}
	
	/**
	 * Start the opening sequence
	 */
	function startOpeningSeq(){
		$('.progressGame').hide();
		openingSeq.reset();
		chooseMenu.reset();
		mainLoop();
	}
	
	/**
	 * End the game
	 *
	 */
	function endGame(){
		stop = true;
		player.startedGame = false;
		fairyticker = 1;
		if(registered == "false"){
			sessionStorage.setItem('myscore', totalScore);
			$('.loginDialog').dialog('open');
		}
		showGameOver();
	}
		 
	/**
	 * Show the progress of assets being loaded
	 * @param {number} progress The number of assets loaded
	 * @param {number} total The total number of assets to load
	 */
	function loadingProgress(progress, total){
		var percent = Math.round(progress / total * 100);
		$('.progressBarGame').attr('value', percent);
		$('.progressPercent').text(percent + "%");
	}
	 
	/**
	 * Function to show the main menu
	 */
	function showMainMenu(){
		$(".progressGame").hide();
		$(".gameMenu").show();
		if(sessionStorage.getItem('myscore') != null && registered == "true"){
			$('.submitScoreDialog').dialog('open');
		}
	}
	
	/**
	 * Main menu start gane click handling
	 */
    $(".startButton").click(function(){
		$(".gameMenu").hide();
		stop = false;
		openingStop = true;
		onChooseMenu = true;
		startGame(chosenFairy);
	});
	
	/**
	 * Function to show the game over menu
	 */
	function showGameOver(){
		ctx.fillStyle = "#AEDDE3";
		clouds = [], fairies = [], items = [], powerups = [], particles = [], presents = [];
		ctx.fillRect(0, 0, canvasWidth, canvasHeight);
		$(".gameScore").text(youScoredText + " " + commaNumber(totalScore));
		ctx.clearRect(0, 0, canvasWidth, canvasHeight);
		if(siteCode == "IE"){
			ctx.drawImage(assetLoader.imgs.openie, 0, 0, canvasWidth, canvasHeight);
		}else{
			ctx.drawImage(assetLoader.imgs.open, 0, 0, canvasWidth, canvasHeight);
		}
		$(".gameOver").show();
	}
	
	/**
	 * Restart game click handling
	 */
	$(".restartGame").click(function(){
		$(".gameOver").hide();
		$('.gameHighScores').hide();
		$("#canvas").css("opacity", 1);
		startGame(chosenFairy);
	});
	
	/**
	 * Back to Main menu click handling
	 */
	$(".backToMainHigh").click(function(){
		$(".gameOver").hide();
		$('.gameHighScores').hide();
		$("#canvas").css("opacity", 1);
		openingStop = true;
		$(".gameMenu").show();
		onHighScoreMenu = false; 
		ctx.clearRect(0, 0, canvasWidth, canvasHeight);
		if(siteCode == "IE"){
			ctx.drawImage(assetLoader.imgs.openie, 0, 0, canvasWidth, canvasHeight);
		}else{
			ctx.drawImage(assetLoader.imgs.open, 0, 0, canvasWidth, canvasHeight);
		}
	});
	
	/**
	 * Instructions Menu click handling
	 */
	$(".instructionButton").click(function(){
		$('.gameMenu').hide();
		$('.instructionMenu').show();
		onInstructionMenu = true;
	});
	
	$(".backToMain").click(function(){
		$('.instructionMenu').hide();
		onInstructionMenu = false;
		$('.gameOver').hide();
		$('.gameMenu').show();
		ctx.clearRect(0, 0, canvasWidth, canvasHeight);
		if(siteCode == "IE"){
			ctx.drawImage(assetLoader.imgs.openie, 0, 0, canvasWidth, canvasHeight);
		}else{
			ctx.drawImage(assetLoader.imgs.open, 0, 0, canvasWidth, canvasHeight);
		}
	});
	
	/**
	 * Submit Score Click Handling
	 */
	$(".submitScore").click(function(){
		submitAScore(totalScore);
	});
	
	function submitAScore(score){
		$('.gameOver').hide();
		$('.gameMenu').hide();
		if($('.playerHighScore span').html() == null || score > parseInt($('.playerHighScore span').html().replace(/\./g, ''))){
			$('.playerHighScore span').html(commaNumber(score));
		}
		url = suburl + "?score=" + HashCode.encode(score.toString());
		jQuery.ajax({
			url		: url,
			datatype : 'json',
			cache	: true,
			success: function(response) {
				onHighScoreMenu = true;
				if(response != "{}"){
					var scoresMap = response.substring(1, response.length-1),
					scores = scoresMap.split(",");
					$('.gameHighScoresList').html("<li class='highScoreTitle'><span>Name</span><span>Score</span></li>");
					var scoresFmt = [];
					for(var i = 0; i < scores.length; i++){
						scoresFmt[i] = scores[i].split("=");
					}
					scoresFmt.sort(function(a, b){
						var bNum = b[0].split('.');
						var aNum = a[0].split('.');
						return aNum[0] - bNum[0];
					});
					for(var i = 0; i < scoresFmt.length; i++){
						scoresFmt[i][1] = commaNumber(scoresFmt[i][1]);
						var score = new HighScore(scoresFmt[i][0], scoresFmt[i][1]);
						$('.gameHighScoresList').append(score.getListItem(i+1));					
					}
				}else{
					$('.noHighScores').remove('.noHighScores');
					$('.gameHighScoresList').append('<div class="noHighScores">No High Scores</div');
				}
				if(siteCode == "IE"){
					ctx.drawImage(assetLoader.imgs.openie, 0, 0, canvasWidth, canvasHeight);
				}else{
					ctx.drawImage(assetLoader.imgs.open, 0, 0, canvasWidth, canvasHeight);
				}
				$('.gameHighScores').show();
			}
		});
	}
	
	function commaNumber(number){
		var num = number.toString();
		var len = num.length;
		var count = 1;
		for(var i = len-1; i >= 0; i--){
			if(count % 3 === 0 && count !== 1 && i !== 0){
				num = spliceString(num, i, num.length, ",");
			}
			count++;
		}
		return num;
	}
	 
	function spliceString(str, idx, c, add){
		return str.slice(0, idx) + (add || "") + str.slice(idx, c);
	}
	
	/**
	 * Submit previous score click handling
	 */
	$('.submitPrevScore').click(function(){
		$('.submitScoreDialog').dialog('close');
		var oldscore = parseInt(sessionStorage.getItem('myscore'));
		onHighScoreMenu = true;
		submitAScore(oldscore);
		sessionStorage.removeItem('myscore');
	});
	 
	/**
	 * High Scores Click Handling
	 */
	$(".highScoreMenu").click(function(){
		jQuery.ajax({
			url		: leaderurl,
			datatype : 'json',
			cache	: true,
			success: function(response) {
				if(response != "{}"){
					var scoresMap = response.substring(1, response.length-1),
					scores = scoresMap.split(",");
					$('.gameHighScoresList').html("<li class='highScoreTitle'><span>Name</span><span>Score</span></li>");
					var scoresFmt = [];
					for(var i = 0; i < scores.length; i++){
						scoresFmt[i] = scores[i].split("=");
					}
					scoresFmt.sort(function(a, b){
						var bNum = b[0].split('.');
						var aNum = a[0].split('.');
						return aNum[0] - bNum[0];
					});
					for(var i = 0; i < scoresFmt.length; i++){
						scoresFmt[i][1] = commaNumber(scoresFmt[i][1]);
						var score = new HighScore(scoresFmt[i][0], scoresFmt[i][1]);
						$('.gameHighScoresList').append(score.getListItem(i+1));
					}
				}else{
					$('.noHighScores').remove('.noHighScores');
					$('.gameHighScoresList').append('<div class="noHighScores">No High Scores</div');
				}
				$('.gameOver').hide();
				$('.gameMenu').hide();
				if(siteCode == "IE"){
					ctx.drawImage(assetLoader.imgs.openie, 0, 0, canvasWidth, canvasHeight);
				}else{
					ctx.drawImage(assetLoader.imgs.open, 0, 0, canvasWidth, canvasHeight);
				}
				$('.gameHighScores').show();
				onHighScoreMenu = true;
			}
		});
	});
	
	/**
	 * High scores class for holding an instance of a high score
	 */
	function HighScore(name, score){
		this.score = score;
		this.name = name;
		
		this.getListItem = function(num){
			var item = '<li class="highScore"><span class="highScoreName">' + this.getEmail + '</span><span class="highScoreScore">' + this.getScore + '</span></li>';
			return item;
		};
		
		return{
			getScore : this.score,
			getEmail : this.name,
			getListItem : this.getListItem
		};
	}
	$(document).ready(function(){
		if(sessionStorage.getItem('myscore') != null){
			var oldscore = sessionStorage.getItem('myscore');
			$('.submitScoreDialogText span').html(oldscore);
		}
		jQuery(".gameCloseIcon").click(function(){						
			$(".loginDialog").dialog('close');
		});	
		
		if(jQuery('.playerHighScore span').html() !== null){
			jQuery('.playerHighScore span').html($('.playerHighScore span').html().replace(/\./g, ','));
		}
		
		jQuery(".submitScoreCloseIcon").click(function(){
			sessionStorage.removeItem('myscore');
			$(".submitScoreDialog").dialog('close');
		});
		
		$(".loginDialog").dialog({ 
			resizable: false,
			minHeight: 250, 
			width: 400,
			height: 'auto',
			modal: true,
			autoOpen: false,
			open: function(event, ui) { $(".ui-dialog-titlebar-close").hide();$(".ui-dialog-titlebar").hide(); },
			position: ['top','center']
		});
		$('.submitScoreDialog').dialog({ 
			resizable: false,
			minHeight: 190,
			width: 350,
			height: 'auto',
			modal: true,
			autoOpen: false,
			open: function(event, ui) { $(".ui-dialog-titlebar-close").hide();$(".ui-dialog-titlebar").hide(); },
			position: ['top','center']
		});

	});
	
})(jQuery);
 
 /**
 *
 *  HashCode encode / decode
 *  http://www.webtoolkit.info/
 *
 **/
  
 var HashCode = {
  
     // private property
     _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  
     // public method for encoding
     encode : function (input) {
         var output = "";
         var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
         var i = 0;
  
         input = HashCode._utf8_encode(input);
  
         while (i < input.length) {
  
             chr1 = input.charCodeAt(i++);
             chr2 = input.charCodeAt(i++);
             chr3 = input.charCodeAt(i++);
  
             enc1 = chr1 >> 2;
             enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
             enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
             enc4 = chr3 & 63;
  
             if (isNaN(chr2)) {
                 enc3 = enc4 = 64;
             } else if (isNaN(chr3)) {
                 enc4 = 64;
             }
  
             output = output +
             this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
             this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
  
         }
  
         return output;
     },
  
     // public method for decoding
     decode : function (input) {
         var output = "";
         var chr1, chr2, chr3;
         var enc1, enc2, enc3, enc4;
         var i = 0;
  
         input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
  
         while (i < input.length) {
  
             enc1 = this._keyStr.indexOf(input.charAt(i++));
             enc2 = this._keyStr.indexOf(input.charAt(i++));
             enc3 = this._keyStr.indexOf(input.charAt(i++));
             enc4 = this._keyStr.indexOf(input.charAt(i++));
  
             chr1 = (enc1 << 2) | (enc2 >> 4);
             chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
             chr3 = ((enc3 & 3) << 6) | enc4;
  
             output = output + String.fromCharCode(chr1);
  
             if (enc3 != 64) {
                 output = output + String.fromCharCode(chr2);
             }
             if (enc4 != 64) {
                 output = output + String.fromCharCode(chr3);
             }
  
         }
  
         output = HashCode._utf8_decode(output);
  
         return output;
  
     },
  
     // private method for UTF-8 encoding
     _utf8_encode : function (string) {
         string = string.replace(/\r\n/g,"\n");
         var utftext = "";
  
         for (var n = 0; n < string.length; n++) {
  
             var c = string.charCodeAt(n);
  
             if (c < 128) {
                 utftext += String.fromCharCode(c);
             }
             else if((c > 127) && (c < 2048)) {
                 utftext += String.fromCharCode((c >> 6) | 192);
                 utftext += String.fromCharCode((c & 63) | 128);
             }
             else {
                 utftext += String.fromCharCode((c >> 12) | 224);
                 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                 utftext += String.fromCharCode((c & 63) | 128);
             }
  
         }
  
         return utftext;
     },
  
     // private method for UTF-8 decoding
     _utf8_decode : function (utftext) {
         var string = "";
         var i = 0;
         var c = c1 = c2 = 0;
  
         while ( i < utftext.length ) {
  
             c = utftext.charCodeAt(i);
  
             if (c < 128) {
                 string += String.fromCharCode(c);
                 i++;
             }
             else if((c > 191) && (c < 224)) {
                 c2 = utftext.charCodeAt(i+1);
                 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                 i += 2;
             }
             else {
                 c2 = utftext.charCodeAt(i+1);
                 c3 = utftext.charCodeAt(i+2);
                 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                 i += 3;
             }
  
         }
  
         return string;
     }
  
 }
